﻿

#include "SCombineTexture.h"
#include "Modules/ModuleManager.h"
#include "PropertyEditorModule.h"
#include "IImageWrapper.h"
#include "IImageWrapperModule.h"
#include "Modules/ModuleManager.h"
#include "HAL/PlatformFilemanager.h"
#include "Misc/FileHelper.h"
#include "Widgets/Input/SButton.h"
#include "Kismet/KismetStringLibrary.h"

FCombineSettingBase::FCombineSettingBase()
{
	FString filePath = FString(FPaths::GamePluginsDir() + "CombineTexture/LocalSettings.txt");
	TArray<FString> Contents;
	if (FFileHelper::LoadFileToStringArray(Contents, *filePath))
	{
		SourceFilter.Path = Contents[0];
		OutFilter.Path = Contents[1];
		BaseColorKey = Contents[2];
		MetallicKey = Contents[3];
		SpecularKey = Contents[4];
		RoughnessKey = Contents[5];
		AmbientOcclusionKey = Contents[6];
		CompressChannel = (EColorCompressChannel)UKismetStringLibrary::Conv_StringToInt(Contents[7]);
		bForceCombine = Contents[8].ToBool();
		bDeleteSourceFile = Contents[9].ToBool();
	}
	else
	{
		SourceFilter.Path = FString("");
		OutFilter.Path = FString("");
		BaseColorKey = FString("BaseColor");
		MetallicKey = FString("Metallic");
		SpecularKey = FString("Specular");
		RoughnessKey = FString("Roughness");
		AmbientOcclusionKey = FString("AmbientOcclusion");
		CompressChannel = EColorCompressChannel::ECC_A;
		bForceCombine = false;
		bDeleteSourceFile = false;
	}
}

void FCombineSettingBase::SaveSettings()
{
	FString filePath = FString(FPaths::GamePluginsDir() + "CombineTexture/LocalSettings.txt");
	TArray<FString> Contents;
	Contents.Empty();
	Contents.Add(SourceFilter.Path);
	Contents.Add(OutFilter.Path);
	Contents.Add(BaseColorKey);
	Contents.Add(MetallicKey);
	Contents.Add(SpecularKey);
	Contents.Add(RoughnessKey);
	Contents.Add(AmbientOcclusionKey);
	Contents.Add(FString::FromInt((int)CompressChannel));
	Contents.Add(UKismetStringLibrary::Conv_BoolToString(bForceCombine));
	Contents.Add(UKismetStringLibrary::Conv_BoolToString(bDeleteSourceFile));
	FFileHelper::SaveStringArrayToFile(Contents, *filePath);
}

FString FCombineSettingBase::GetOutFilter()
{
	FString Te = OutFilter.Path.Mid(OutFilter.Path.Len() - 1);
	if (Te == FString("/"))
	{
		return OutFilter.Path;
	}
	else
	{
		return OutFilter.Path + FString("/");
	}
}


DEFINE_LOG_CATEGORY(CombineTextureLog);
#define LOCTEXT_NAMESPACE "SCombineTextureCreateTexture"


void SCombineTexture::Construct(const FArguments& InArgs)
{
	CombineSettingBase = MakeShareable(new FCombineSettingBase);
	CreateExportFilterListView();
	ChildSlot
		[
			SNew(SVerticalBox)
			+ SVerticalBox::Slot()
		.AutoHeight()
		[
			SNew(SHorizontalBox)
			+ SHorizontalBox::Slot()
		.VAlign(VAlign_Center)
		[
			SettingsView->GetWidget()->AsShared()
		]
		]
	+ SVerticalBox::Slot()
		.AutoHeight()
		[
			SNew(SButton)
			.Text(LOCTEXT("CombineTexture", "CombineTexture"))
		.OnClicked(this, &SCombineTexture::CombineTexture)
		]
		];
}

void SCombineTexture::CreateExportFilterListView()
{
	// Create a property view
	FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked<FPropertyEditorModule>("PropertyEditor");

	FDetailsViewArgs DetailsViewArgs;
	{
		DetailsViewArgs.bAllowSearch = true;
		DetailsViewArgs.bHideSelectionTip = true;
		DetailsViewArgs.bLockable = false;
		DetailsViewArgs.bSearchInitialKeyFocus = true;
		DetailsViewArgs.bUpdatesFromSelection = false;
		DetailsViewArgs.NotifyHook = nullptr;
		DetailsViewArgs.bShowOptions = true;
		DetailsViewArgs.bShowModifiedPropertiesOption = false;
		DetailsViewArgs.bShowScrollBar = false;
		DetailsViewArgs.bShowOptions = true;
	}

	FStructureDetailsViewArgs StructureViewArgs;
	{
		StructureViewArgs.bShowObjects = true;
		StructureViewArgs.bShowAssets = true;
		StructureViewArgs.bShowClasses = true;
		StructureViewArgs.bShowInterfaces = true;
	}

	SettingsView = EditModule.CreateStructureDetailView(DetailsViewArgs, StructureViewArgs, nullptr);
	FStructOnScope* Struct = new FStructOnScope(FCombineSettingBase::StaticStruct(), (uint8*)CombineSettingBase.Get());
	SettingsView->SetStructureData(MakeShareable(Struct));
}

FReply SCombineTexture::CombineTexture() const
{
	FString BaseColorPath = FString("");
	FString MetallicPath = FString("");
	FString SpecularPath = FString("");
	FString RoughnessPath = FString("");
	FString AmbientOcclusionPath = FString("");
	if (!GetAllFilePathAtSourceFilter(BaseColorPath, MetallicPath, SpecularPath, RoughnessPath, AmbientOcclusionPath))
	{
		UE_LOG(CombineTextureLog, Error, TEXT("Not Find Texture Path !"));
		return FReply::Handled();
	}

	TArray<uint8> BaseColor = GetLocalTexture(BaseColorPath);
	if (!BaseColor.IsValidIndex(0))
	{
		UE_LOG(CombineTextureLog, Error, TEXT("BaseColor Texture Load Fiald"));
		return FReply::Handled();
	}

	int CombineLength = (int)(FMath::Sqrt((float)(BaseColor.Num() / 4)));
	bool bCompressSucceed = false;

	TArray<uint8> Metallic = CompressTexture(GetLocalTexture(MetallicPath), CombineLength, bCompressSucceed);
	if (!bCompressSucceed)
	{
		if (!CombineSettingBase->bForceCombine)
		{
			UE_LOG(CombineTextureLog, Error, TEXT("Metallic Texture Disaffinity Length "));
			return FReply::Handled();
		}
		UE_LOG(CombineTextureLog, Warning, TEXT("Metallic Texture Disaffinity Length "));
		Metallic.SetNum(BaseColor.Num() / 4);
	}

	TArray<uint8> Specular = CompressTexture(GetLocalTexture(SpecularPath), CombineLength, bCompressSucceed);
	if (!bCompressSucceed)
	{
		if (!CombineSettingBase->bForceCombine)
		{
			UE_LOG(CombineTextureLog, Error, TEXT("Specular Texture Disaffinity Length "));
			return FReply::Handled();
		}
		UE_LOG(CombineTextureLog, Warning, TEXT("Specular Texture Disaffinity Length "));
		Specular.SetNum(BaseColor.Num() / 4);
	}

	TArray<uint8> Roughness = CompressTexture(GetLocalTexture(RoughnessPath), CombineLength, bCompressSucceed);
	if (!bCompressSucceed)
	{
		if (!CombineSettingBase->bForceCombine)
		{
			UE_LOG(CombineTextureLog, Error, TEXT("Roughness Texture Disaffinity Length "));
			return FReply::Handled();
		}
		UE_LOG(CombineTextureLog, Warning, TEXT("Roughness Texture Disaffinity Length "));
		Roughness.SetNum(BaseColor.Num() / 4);
	}

	TArray<uint8> AmbientOcclusion = CompressTexture(GetLocalTexture(AmbientOcclusionPath), CombineLength, bCompressSucceed);
	if (!bCompressSucceed)
	{
		if (!CombineSettingBase->bForceCombine)
		{
			UE_LOG(CombineTextureLog, Error, TEXT("AmbientOcclusion Texture Disaffinity Length "));
			return FReply::Handled();
		}
		UE_LOG(CombineTextureLog, Warning, TEXT("AmbientOcclusion Texture Disaffinity Length "));
		AmbientOcclusion.SetNum(BaseColor.Num() / 4);
	}


	TArray<uint8>* CombineTexture = new TArray<uint8>();

	for (size_t y = 0; y < CombineLength; y++)
	{
		for (size_t x = 0; x < CombineLength; x++)
		{
			int index = (y * CombineLength + x) * 4;
			CombineTexture->Add(BaseColor[index]);
			index++;
			CombineTexture->Add(BaseColor[index]);
			index++;
			CombineTexture->Add(BaseColor[index]);

			if (x < CombineLength / 2)
			{
				if (y < CombineLength / 2)
				{
					index = (y * CombineLength / 2 + x) * 4 + 3;
					CombineTexture->Add(Metallic[index]);
				}
				else
				{
					index = ((y - CombineLength / 2) * CombineLength / 2 + x) * 4 + 3;
					CombineTexture->Add(Roughness[index]);
				}
			}
			else
			{
				if (y < CombineLength / 2)
				{
					index = (y * CombineLength / 2 + (x - CombineLength / 2)) * 4 + 3;
					CombineTexture->Add(Specular[index]);
				}
				else
				{
					index = ((y - CombineLength / 2) * CombineLength / 2 + (x - CombineLength / 2)) * 4 + 3;
					CombineTexture->Add(AmbientOcclusion[index]);
				}
			}
		}
	}

	ExportTexture(CombineTexture, GetOutFileName(BaseColorPath), CombineLength);

	CombineSettingBase->SaveSettings();

	return FReply::Handled();
}

bool SCombineTexture::GetAllFilePathAtSourceFilter(FString& OutBaseColorPath, FString& OutMetallicPath, FString& OutSpecularPath, FString& OutRoughnessPath, FString& OutAmbientOcclusionPath) const
{
	FString fileDic = CombineSettingBase->SourceFilter.Path;
	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();

	TArray<FString> Files;
	PlatformFile.FindFiles(Files, *fileDic, NULL);
	for (size_t i = 0; i < Files.Num(); i++)
	{
		const FString& FilePath = Files[i];
		if (OutBaseColorPath == FString(""))
		{
			if (FilePath.Contains(CombineSettingBase->BaseColorKey, ESearchCase::IgnoreCase, ESearchDir::FromEnd))
			{
				OutBaseColorPath = FilePath;
			}
		}
		if (OutMetallicPath == FString(""))
		{
			if (FilePath.Contains(CombineSettingBase->MetallicKey, ESearchCase::IgnoreCase, ESearchDir::FromEnd))
			{
				OutMetallicPath = FilePath;
			}
		}
		if (OutSpecularPath == FString(""))
		{
			if (FilePath.Contains(CombineSettingBase->SpecularKey, ESearchCase::IgnoreCase, ESearchDir::FromEnd))
			{
				OutSpecularPath = FilePath;
			}
		}
		if (OutRoughnessPath == FString(""))
		{
			if (FilePath.Contains(CombineSettingBase->RoughnessKey, ESearchCase::IgnoreCase, ESearchDir::FromEnd))
			{
				OutRoughnessPath = FilePath;
			}
		}
		if (OutAmbientOcclusionPath == FString(""))
		{
			if (FilePath.Contains(CombineSettingBase->AmbientOcclusionKey, ESearchCase::IgnoreCase, ESearchDir::FromEnd))
			{
				OutAmbientOcclusionPath = FilePath;
			}
		}
	}

	if (OutBaseColorPath == FString(""))
	{
		UE_LOG(CombineTextureLog, Error, TEXT("Not Find File By Key: BaseColor"));
		return false;
	}
	if (OutMetallicPath == FString(""))
	{
		if (!CombineSettingBase->bForceCombine)
		{
			UE_LOG(CombineTextureLog, Error, TEXT("Not Find File By Key: Metallic"));
			return false;
		}
		UE_LOG(CombineTextureLog, Warning, TEXT("Not Find File By Key: Metallic"));
	}
	if (OutSpecularPath == FString(""))
	{
		if (!CombineSettingBase->bForceCombine)
		{
			UE_LOG(CombineTextureLog, Error, TEXT("Not Find File By Key: Specular"));
			return false;
		}
		UE_LOG(CombineTextureLog, Warning, TEXT("Not Find File By Key: Specular"));
	}
	if (OutRoughnessPath == FString(""))
	{
		if (!CombineSettingBase->bForceCombine)
		{
			UE_LOG(CombineTextureLog, Error, TEXT("Not Find File By Key: Roughness"));
			return false;
		}
		UE_LOG(CombineTextureLog, Warning, TEXT("Not Find File By Key: Roughness"));
	}
	if (OutAmbientOcclusionPath == FString(""))
	{
		if (!CombineSettingBase->bForceCombine)
		{
			UE_LOG(CombineTextureLog, Error, TEXT("Not Find File By Key: AmbientOcclusion"));
			return false;
		}
		UE_LOG(CombineTextureLog, Warning, TEXT("Not Find File By Key: AmbientOcclusion"));
	}
	return true;
}

TArray<uint8> SCombineTexture::GetLocalTexture(const FString& _TexPath) const
{
	IImageWrapperModule& imageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
	IImageWrapperPtr imageWrapper = imageWrapperModule.CreateImageWrapper(EImageFormat::PNG);

	TArray<uint8> OutArray;
	if (FFileHelper::LoadFileToArray(OutArray, *_TexPath))
	{
		if (imageWrapper.IsValid() &&
			imageWrapper->SetCompressed(OutArray.GetData(), OutArray.Num()))
		{
			const TArray<uint8>* uncompressedRGBA = NULL;
			if (imageWrapper->GetRaw(ERGBFormat::RGBA, 8, uncompressedRGBA))
			{
				return *uncompressedRGBA;
			}
		}
	}
	return TArray<uint8>();
}

TArray<uint8> SCombineTexture::CompressTexture(const TArray<uint8> SourceTexture, const int CombineLength, bool& succeed) const
{
	if (SourceTexture.Num() != CombineLength * CombineLength * 4)
	{
		UE_LOG(CombineTextureLog, Error, TEXT("Compress Texture Faild : Texture Length Is Disaffinity "));
		succeed = false;
		return TArray<uint8>();
	}
	TArray<uint8> Result;
	Result.Empty();

	int Addedindex = 0;
	switch (CombineSettingBase->CompressChannel)
	{
	case EColorCompressChannel::ECC_R:
		Addedindex = 0;
		break;
	case EColorCompressChannel::ECC_G:
		Addedindex = 1;
		break;
	case EColorCompressChannel::ECC_B:
		Addedindex = 2;
		break;
	case EColorCompressChannel::ECC_A:
		Addedindex = 3;
		break;
	default:
		Addedindex = 3;
		break;
	}
	for (size_t y = 0; y < CombineLength / 2; y++)
	{
		for (size_t x = 0; x < CombineLength / 2; x++)
		{
			int index = ((y * 2 * CombineLength) + x * 2) * 4;
			int index2 = index + 4;
			int index3 = index + CombineLength * 4;
			int index4 = index3 + 4;

			uint8 value = SourceTexture[index + Addedindex] / 4 + SourceTexture[index2 + Addedindex] / 4 + SourceTexture[index3 + Addedindex] / 4 + SourceTexture[index4 + Addedindex] / 4;

			switch (CombineSettingBase->CompressChannel)
			{
			case EColorCompressChannel::ECC_R:
				Result.Add(value); Result.Add(0); Result.Add(0); Result.Add(0);
				break;
			case EColorCompressChannel::ECC_G:
				Result.Add(0); Result.Add(value); Result.Add(0); Result.Add(0);
				break;
			case EColorCompressChannel::ECC_B:
				Result.Add(0); Result.Add(0); Result.Add(value); Result.Add(0);
				break;
			case EColorCompressChannel::ECC_A:
				Result.Add(0); Result.Add(0); Result.Add(0); Result.Add(value);
				break;
			default:
				break;
			}
		}
	}
	succeed = true;
	return Result;
}

void SCombineTexture::ExportTexture(const TArray<uint8>* uncompressedRGBA, const FString& FileName, const int& TextureLength) const
{
	FArchive* Ar = IFileManager::Get().CreateFileWriter(*FString(CombineSettingBase->GetOutFilter() + FileName + FString(".PNG")));

	IImageWrapperModule& ImageWrapperModuleTT = FModuleManager::Get().LoadModuleChecked<IImageWrapperModule>(TEXT("ImageWrapper"));

	TSharedPtr<IImageWrapper> PNGImageWrapper = ImageWrapperModuleTT.CreateImageWrapper(EImageFormat::PNG);

	PNGImageWrapper->SetRaw(uncompressedRGBA->GetData(), uncompressedRGBA->GetAllocatedSize(), TextureLength, TextureLength, ERGBFormat::RGBA, 8);

	const TArray<uint8>& PNGData = PNGImageWrapper->GetCompressed(100);

	Ar->Serialize((void*)PNGData.GetData(), PNGData.GetAllocatedSize());

	Ar->Close();
}

FString SCombineTexture::GetOutFileName(const FString& BaseColorPath) const
{
	int endindex = BaseColorPath.Find(CombineSettingBase->BaseColorKey, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
	int startindex = BaseColorPath.Find(FString("/"), ESearchCase::IgnoreCase, ESearchDir::FromEnd);
	return BaseColorPath.Mid(startindex + 1, endindex - startindex - 2);
}

#undef LOCTEXT_NAMESPACE


